package ynet

import (
	"errors"
	"fmt"
	"golang-zinx-demo/yanx/utils"
	"golang-zinx-demo/yanx/yiface"
	"io"
	"net"
	"sync"
)

/*
	链接模块
*/
type Connection struct {
	// 当前Conn是属于哪个Server
	TcpServer yiface.IServer
	// 当前链接的socket TCP套接字
	Conn *net.TCPConn

	// 链接的ID
	ConnID uint32

	// 当前链接的状态
	isClosed bool

	// 告知当前链接已经 退出/停止 的channel(由Reader告知Writer退出)
	ExitChan chan bool

	// 无缓冲的通道，用于读、写Goroutine之间的通信
	msgChan chan []byte

	// 当前的Server的消息管理模块，用来绑定MsgID和对应的处理业务API
	MsgHandler yiface.IMsgHandle

	// 链接属性集合
	property map[string]interface{}
	// 保护链接属性的锁
	propertyLock sync.RWMutex
}

func (s *Server) GetConnMgr() yiface.IConnManager {
	return s.ConnMgr
}

// 初始化链接模块的方法
func NewConnection(server yiface.IServer, conn *net.TCPConn, connID uint32, msgHandler yiface.IMsgHandle) *Connection {
	c := &Connection{
		TcpServer:  server,
		Conn:       conn,
		ConnID:     connID,
		MsgHandler: msgHandler,
		isClosed:   false,
		ExitChan:   make(chan bool, 1),
		msgChan:    make(chan []byte),
		property:   make(map[string]interface{}),
	}
	// 将当前conn加入到ConnManager中
	c.TcpServer.GetConnMgr().Add(c)
	return c
}

// 链接的读业务方法
func (c *Connection) StartReader() {
	fmt.Println("[Reader Goroutine is running]")
	defer fmt.Printf("ConnID = %d，[Reader is exit!]，remote addr is %s\n", c.ConnID, c.RemoteAddr().String())
	defer c.Stop()
	for {
		/* // 读取客户端的数据到buf中，目前最大512字节
		buf := make([]byte, utils.GlobalObject.MaxPackageSize)
		_, err := c.Conn.Read(buf)
		if err != nil {
			fmt.Println("recv buf err: ", err)
			continue
		} */

		// 创建一个拆包解包的对象
		dp := NewDataPack()

		// 读取客户端的MsgHead 二进制流 8个字节
		headData := make([]byte, dp.GetHeadLen())
		if _, err := io.ReadFull(c.GetTCPConnection(), headData); err != nil {
			fmt.Println("read msg head error: ", err)
			break
		}

		// 拆包 得到MsgID和MsgDataLen，放在一个msg消息对象中
		msg, err := dp.UnPack(headData)
		if err != nil {
			fmt.Println("unpack error: ", err)
			break
		}
		// 根据dataLen再次读取数据
		var data []byte
		if msg.GetMsgLen() > 0 {
			data = make([]byte, msg.GetMsgLen())
			if _, err := io.ReadFull(c.GetTCPConnection(), data); err != nil {
				fmt.Println("read msg data error: ", err)
				break
			}
		}
		msg.SetMsgData(data)

		// 创建当前Conn的Request请求数据对象

		req := &Request{
			conn: c,
			msg:  msg,
		}

		if utils.GlobalObject.WorkPoolSize > 0 {
			// 已经开启了工作池机制，将消息发送给Worker工作池处理即可
			c.MsgHandler.SendMsgToTaskQueue(req)

		} else {
			// 从路由中，找到注册绑定的Conn对应的router调用
			// 根据msgID找到对应的api业务，执行
			go c.MsgHandler.DoMsgHandle(req)
		}

	}
}

/*
	写消息Goroutine，专门发送给客户端消息的模块
*/
func (c *Connection) StartWriter() {
	fmt.Println("[Write Goroutine is running]")
	defer fmt.Println(c.RemoteAddr().String(), "[conn write exit!]")

	// 不断的阻塞的等待channel的消息，进行写给客户端
	for {
		select {
		case data := <-c.msgChan:
			// 有数据要写给客户端
			fmt.Println("Writer:", string(data))
			if _, err := c.Conn.Write(data); err != nil {
				fmt.Println("Send data error: ", err)
				return
			}

			// 代表Reader已经退出，此时Writer也要退出
		case <-c.ExitChan:
			return
		}

	}
}

// 启动链接 让当前的链接准备开始工作
func (c *Connection) Start() {
	fmt.Println("Conn Start()... ConnID = ", c.ConnID)
	// 启动从当前链接读数据的业务
	go c.StartReader()
	// 启动从当前链接写数据的业务
	go c.StartWriter()

	// 按照开发者传递进来的 创建链接之后需要调用的业务处理，执行Hook函数
	c.TcpServer.CallOnConnStart(c)
}

// 停止链接 结束当前链接的工作
func (c *Connection) Stop() {
	fmt.Println("Conn Stop()... ConnID = ", c.ConnID)

	// 如果当前链接已经关闭
	if c.isClosed {
		return
	}
	// 按照开发者传递进来的 销毁链接链接之前需要调用的业务处理，执行Hook函数
	c.TcpServer.CallOnConnStop(c)

	c.isClosed = true

	// 关闭socket链接
	c.Conn.Close()

	// 告知Writer关闭
	c.ExitChan <- true

	// 将当前连接从ConnMgr中删除
	c.TcpServer.GetConnMgr().Remove(c)

	// 关闭管道
	close(c.ExitChan)
	close(c.msgChan)
}

// 获取当前链接绑定的socket conn
func (c *Connection) GetTCPConnection() *net.TCPConn {
	return c.Conn
}

// 获取当前链接模块的链接ID
func (c *Connection) GetConnID() uint32 {
	return c.ConnID
}

// 获取客户端的 TCP状态 IP port
func (c *Connection) RemoteAddr() net.Addr {
	return c.Conn.RemoteAddr()
}

// 提供一个SendMsg方法 将我们要发送给客户端的数据先进行封包，再发送
func (c *Connection) SendMsg(msgID uint32, data []byte) error {
	if c.isClosed {
		return errors.New("Connection closed  when sendMsg")
	}

	// 将data进行封包 MagDataLen/MsgID/MsgData
	db := NewDataPack()
	binaryMsg, err := db.Pack(NewMessagePackage(msgID, data))
	if err != nil {
		fmt.Println("pack message id = ", msgID)
		return errors.New("pack message error")
	}

	// 将数据发送给客户端
	c.msgChan <- binaryMsg

	return nil
}

// 设置链接属性
func (c *Connection) SetProperty(key string, value interface{}) {
	c.propertyLock.Lock()
	defer c.propertyLock.Unlock()

	// 设置属性
	c.property[key] = value
}

// 获取链接属性
func (c *Connection) GetProperty(key string) (interface{}, error) {
	c.propertyLock.RLock()
	defer c.propertyLock.RUnlock()

	// 读取属性
	if value, ok := c.property[key]; ok {
		return value, nil
	}
	return nil, errors.New(fmt.Sprintf("property (%s) is not found", key))
}

// 移除链接属性
func (c *Connection) RomoveProperty(key string) {
	c.propertyLock.Lock()
	defer c.propertyLock.Unlock()
	// 移除属性
	delete(c.property, key)
}
